//***************************************************************************
//   Copyright(c)2020, Xidian University D405.
//           All rights reserved
//
//   File name       :   axi_master_write_channel_fsm.v
//   Module name     :   axi_master_write_channel_fsm
//   Author          :   Zhao Yuchen
//   Date            :   2022/06/24
//   Version         :   v0.95
//   Edited by       :   Zhao Yuchen
//---------------------------------------------------------------------------
// Changelog : See axi_master_interface_wrapper.v
// Interface list :
//                AXI Master
//                FIFO Control
//***************************************************************************
//`define AXI_UNALIGN_EN
//`define VCS_MODEL
//`undef VCS_MODEL
//`define SIMULATION
module axi_master_write_channel_fsm #
(
    //parameter integer AXI_UNALIGN_ADDR_EN   = 1,
    parameter integer AXI_BURST_LEN         = 256,
	parameter integer AXI_ID_WIDTH          = 4,
	parameter integer AXI_ADDR_WIDTH        = 32,
	parameter integer AXI_DATA_WIDTH        = 128,
    parameter integer AXI_LIB_WIDTH         = 13, //Width of Max Transmit Length(in BYTES)
	parameter integer AXI_AWUSER_WIDTH      = 0,
	parameter integer AXI_ARUSER_WIDTH      = 0,
	parameter integer AXI_WUSER_WIDTH       = 0,
	parameter integer AXI_RUSER_WIDTH       = 0,
	parameter integer AXI_BUSER_WIDTH       = 0,
	parameter integer AXI_MAX_INSTR_NUM     = 32 //Max transaction(outstanding) number
)
(
    input  wire                           M_AXI_ACLK_I,      //AXI clock input
	input  wire                           M_AXI_ARESETN_I,   //AXI master rstn input
	//AXI Address Writing ch. signals
	output wire [AXI_ID_WIDTH-1 : 0]      M_AXI_AWID_O,
	output wire [AXI_ADDR_WIDTH-1 : 0]    M_AXI_AWADDR_O,
	output wire [7 : 0]                   M_AXI_AWLEN_O,
	output wire [2 : 0]                   M_AXI_AWSIZE_O,
	output wire [1 : 0]                   M_AXI_AWBURST_O,
	output wire                           M_AXI_AWLOCK_O,
	output wire [3 : 0]                   M_AXI_AWCACHE_O,
	output wire [2 : 0]                   M_AXI_AWPROT_O,
	output wire [3 : 0]                   M_AXI_AWQOS_O,
	output wire [AXI_AWUSER_WIDTH-1 : 0]  M_AXI_AWUSER_O,
	output wire                           M_AXI_AWVALID_O,
	input  wire                           M_AXI_AWREADY_I,
    //AXI Data Writing ch. signals
	output wire [AXI_DATA_WIDTH-1 : 0]    M_AXI_WDATA_O,
	output wire [AXI_DATA_WIDTH/8-1 : 0]  M_AXI_WSTRB_O,
	output wire                           M_AXI_WLAST_O,
	output wire [AXI_WUSER_WIDTH-1 : 0]   M_AXI_WUSER_O,
	output wire                           M_AXI_WVALID_O,
	input  wire                           M_AXI_WREADY_I,
    //AXI write response ch. signals
	input  wire [AXI_ID_WIDTH-1 : 0]      M_AXI_BID_I,
	input  wire [1 : 0]                   M_AXI_BRESP_I,
	input  wire [AXI_BUSER_WIDTH-1 : 0]   M_AXI_BUSER_I,
	input  wire                           M_AXI_BVALID_I,
	output wire                           M_AXI_BREADY_O,
	
	//******  [External Ports] Wirte Channel  ******//
	input  wire [AXI_ID_WIDTH-1 : 0]      write_id_i,                //write transaction id input
	input  wire [AXI_ADDR_WIDTH-1 : 0]    write_addr_i,              //write transaction address input
	input  wire [AXI_LIB_WIDTH-1 : 0]     write_len_in_byte_i,       //write transaction length(in byte) input
	input  wire                           write_desc_en_i,           //write descriptor enable
	input  wire                           write_desc_type_i,
	input  wire [AXI_DATA_WIDTH-1 : 0]    write_data_i,              //write data input(from an external TxFIFO)
	output wire                           write_ready_o,             //receive write command ready
	input  wire                           start_write_i,             //start a write transaction(input a write command) use posedge or a pulse input

	output reg                            write_fifo_rd_en_o,        //control the external TxFIFO, fetch data to transmit to AXI slave
	input  wire                           write_fifo_rd_empty_i,     //TxFIFO empty signal. (control WVALID low)
	input  wire                           write_fifo_rd_valid_i,
	                                                                 //if TxFIFO is empty when AXI master transmitting, it usually cause pause.
	output wire [1 : 0]                   write_data_or_desc_sel_o,  //01:data  10:descriptor 00&11:undefine
	output wire [1 : 0]                   write_desc_fifo_sel_o,
	
	output wire                           write_data_done_o,
	output wire                           write_desc_done_o,
	output wire                           write_data_almost_done_o,
	output wire                           write_desc_almost_done_o,
	output wire [5 : 0]                   write_fsm_state_o,
    output reg                            write_active_total_o
`ifndef VCS_MODEL
    ,input  wire [9 : 0]                  ram_2p_cfg_register,
    input  wire [6 : 0]                   rf_2p_cfg_register
`endif                                                   
);  

`ifdef AXI_SIMULATION
    always
    begin
        @(posedge start_write_i)
        begin
            if(write_desc_en_i == 1'b0)
            begin
                $display("AXI write transaction, len of bytes = %d.", write_len_in_byte_i + 1);
            end
        end
    end
`endif
   
    function integer clogb2 (input integer bit_depth);              
    begin:bit_width_calc
        integer bit_depth_tmp;    
        bit_depth_tmp = bit_depth;                                         
        for(clogb2=0; bit_depth_tmp>0; clogb2=clogb2+1)                   
            bit_depth_tmp = bit_depth_tmp >> 1;                                 
    end                                                           
    endfunction    
    
    //localparam integer AXI_UNALIGN_ADDR_EN     = 1;
    localparam integer BST_CNT_WIDTH       = clogb2(AXI_BURST_LEN);                             //burst counter width    
    localparam integer TRN_CNT_WITDH       = clogb2(AXI_MAX_INSTR_NUM);                         //transaction counter width
    localparam integer DOCW                = clogb2(AXI_DATA_WIDTH/8);                            //DATA_OFFSET_CNT_WIDTH
    localparam integer INSTR_WIDTH         = AXI_ID_WIDTH + AXI_ADDR_WIDTH + AXI_LIB_WIDTH;     //instruction(a.k.a. write command) width, includes id\addr\len etc.
    localparam integer ADDR_ALIGN_BOUNDARY = AXI_DATA_WIDTH / 'd8;                              //addr and data align boundary
    localparam integer ADDR2LEN_BIT        = $unsigned(clogb2(ADDR_ALIGN_BOUNDARY)) - 'd1;      //used to calculate burst length
    localparam integer DATAFSM_FIFO_DW     = INSTR_WIDTH+AXI_LIB_WIDTH-AXI_ID_WIDTH+4;          //data fsm fifo data width
    localparam integer BRESPFSM_FIFO_DW    = $unsigned(AXI_ID_WIDTH);                                      //bresp fsm fifo data width
    localparam integer FIFO_LINE_LEN_BYTE  = 'd16;
    
    //parameter of write channel FSM state
    localparam  W_IDLE        = 6'b000001;
    localparam  W_FETCH_INSTR = 6'b000010;
    localparam  W_WADDR       = 6'b000100;
    localparam  W_WDATA       = 6'b001000;
    localparam  W_CHKID       = 6'b010000;
    localparam  W_PAUSE       = 6'b100000;
    
    reg  [5:0]                      w_addr_state;
    reg  [5:0]                      w_addr_next_state;
    reg  [5:0]                      w_data_state;
    reg  [5:0]                      w_data_next_state;
    reg  [5:0]                      w_data_state_r;
    reg  [5:0]                      w_bresp_state;
    reg  [5:0]                      w_bresp_next_state;
    reg                             axi_awvalid;
    reg  [AXI_ADDR_WIDTH-1 : 0]     axi_awaddr;
    reg                             axi_wvalid;
    reg  [AXI_DATA_WIDTH-1 : 0]     wdata;
    reg                             write_active;
    wire                            wnext;
    reg  [BST_CNT_WIDTH-1 : 0]      write_index;
    reg  [BST_CNT_WIDTH-1 : 0]      write_index_total;
    reg  [BST_CNT_WIDTH-1 : 0]      write_index_total_ff;
    reg  [AXI_DATA_WIDTH/8-1 : 0]   axi_wstrb;   //axi write_strobe
    reg                             axi_wlast;
    reg                             axi_bready;
    reg                             axi_bready_ff;
    wire                            axi_bready_pos;
    reg  [7 : 0]                    write_len_for_addr_ch;
    reg  [7 : 0]                    write_len_for_data_ch;
    reg  [7 : 0]                    write_len_total_for_data_ch;
    wire [TRN_CNT_WITDH:0]          write_transaction_cnt_wch;
    wire [TRN_CNT_WITDH:0]          write_transaction_cnt_bch;
    wire                            write_addr_done;
    reg                             write_data_done;
    reg                             bresp_done_ff;
    wire                            bresp_done;
    wire                            bchk_ready;
    wire [AXI_ID_WIDTH-1 : 0]       bchk_id;
    wire [AXI_ADDR_WIDTH-1 : 0]     bchk_addr;
    wire [AXI_ADDR_WIDTH-1 : 0]     bchk_addr_align;
    wire [AXI_LIB_WIDTH-1 : 0]      bchk_bytes;
    wire [AXI_LIB_WIDTH-1 : 0]      bchk_bytes_total;
    wire                            bchk_wdesc_en; 
    wire                            bchk_wdesc_type;  
    wire                            bchk_begin_of_a_task; 
    wire                            bchk_end_of_a_task;
    wire [AXI_ADDR_WIDTH-1 : 0]     data_fsm_addr;
    wire [AXI_ADDR_WIDTH-1 : 0]     data_fsm_addr_end_part;
    wire [AXI_ADDR_WIDTH-1 : 0]     data_fsm_addr_end_total;
    wire [AXI_LIB_WIDTH-1 : 0]      data_fsm_bytes;
    wire [AXI_LIB_WIDTH-1 : 0]      data_fsm_bytes_total;
    wire                            data_fsm_wdesc_en;
    wire                            data_fsm_wdesc_type;
    wire                            data_fsm_begin_of_a_task;
    wire                            data_fsm_end_of_a_task;
    wire [AXI_ID_WIDTH-1 : 0]       bresp_fsm_id;
    wire                            instr_fifo_wr_en;
    reg                             instr_fifo_rd_en;
    wire                            instr_fifo_empty;
    wire                            instr_fifo_full;
    wire [AXI_ID_WIDTH-1 : 0]       instr_id;
    wire [AXI_ADDR_WIDTH-1 : 0]     instr_addr;
    wire [AXI_ADDR_WIDTH-1 : 0]     instr_addr_end;
    wire [AXI_LIB_WIDTH-1 : 0]      instr_bytes;
    wire                            start_write_data_pulse;
    reg                             start_write_data_pulse_ff;
    reg                             start_write_data_pulse_r;
    reg                             write_fifo_rd_en_ff1; //Change this signal to fifo dout valid
    reg                             write_fifo_rd_en_ff2;
    wire [AXI_DATA_WIDTH/8-1 : 0]   wstrb_last;
    wire [AXI_DATA_WIDTH/8-1 : 0]   last_data_valid_byte;
    reg                             write_almost_done;
    reg                             write_almost_done_ff;
    wire                            write_fifo_valid;
    reg                             write_fifo_valid_ff;
    reg  [AXI_LIB_WIDTH-1 : 0]      fifo_rd_cnt;
    reg  [AXI_LIB_WIDTH-1 : 0]      fifo_rd_done_cnt;
    reg  [AXI_LIB_WIDTH-1 : 0]      axi_wr_cnt;
    reg                             start_write_r;
    reg                             last_data_accepted;


    assign M_AXI_AWID_O     = instr_id;
    assign M_AXI_AWADDR_O   = instr_addr;
    assign M_AXI_AWLEN_O    = write_len_for_addr_ch;
    assign M_AXI_AWSIZE_O   = $unsigned(clogb2((AXI_DATA_WIDTH/8))-1); //16B -> 2^4 -> SIZE=4
    assign M_AXI_AWBURST_O  = 2'b01; //INCR MODE
    assign M_AXI_AWLOCK_O   = 1'b0;
    assign M_AXI_AWCACHE_O  = 4'b0010;
    assign M_AXI_AWPROT_O   = 3'h0;
    assign M_AXI_AWQOS_O    = 4'h0;
    assign M_AXI_AWUSER_O   = 'b1;
    assign M_AXI_AWVALID_O  = axi_awvalid;
    
    assign M_AXI_WDATA_O    = wdata;
    assign M_AXI_WSTRB_O    = axi_wstrb;
    assign M_AXI_WLAST_O    = axi_wlast;
    assign M_AXI_WUSER_O    = 'b0;
    assign M_AXI_WVALID_O   = axi_wvalid;
    assign M_AXI_BREADY_O   = axi_bready;
    
    assign write_fsm_state_o        = w_data_state;
    assign wstrb_last = (last_data_valid_byte == 'd0) ? ({(AXI_DATA_WIDTH/8){1'b1}}) : ({(AXI_DATA_WIDTH/8){1'b1}} >> ((AXI_DATA_WIDTH/8)-last_data_valid_byte));
    assign last_data_valid_byte = ((data_fsm_bytes+1) % ADDR_ALIGN_BOUNDARY == 'd0) ? (AXI_DATA_WIDTH/8) : ((data_fsm_bytes+1) % ADDR_ALIGN_BOUNDARY);
    assign write_ready_o            = bchk_ready & ~start_write_r;
    assign write_addr_done          = M_AXI_AWREADY_I && axi_awvalid;
    assign instr_addr_end           = instr_addr + instr_bytes;
    assign start_write_data_pulse   = (w_data_state == W_WDATA) && (w_data_state_r == W_IDLE) || (write_data_done && (write_transaction_cnt_wch > 'd1));
    assign data_fsm_addr_end_part   = data_fsm_addr + data_fsm_bytes;
    assign data_fsm_addr_end_total  = data_fsm_addr + data_fsm_bytes_total;
    assign write_data_or_desc_sel_o = (data_fsm_wdesc_en) ? 2'b10 : 2'b01;
    assign write_desc_fifo_sel_o    = (data_fsm_wdesc_type) ? 2'b10 : 2'b01;
    assign wnext                    = M_AXI_WREADY_I && M_AXI_WVALID_O;
    assign write_data_done_o        = ~data_fsm_wdesc_en && write_data_done && data_fsm_end_of_a_task;
    assign write_desc_done_o        = data_fsm_wdesc_en && write_data_done && data_fsm_end_of_a_task;
    assign bresp_done               = ~bresp_done_ff && M_AXI_BVALID_I && axi_bready;
    assign write_data_almost_done_o = ~data_fsm_wdesc_en && ~write_almost_done_ff && write_almost_done && data_fsm_end_of_a_task;
    assign write_desc_almost_done_o = data_fsm_wdesc_en && ~write_almost_done_ff && write_almost_done && data_fsm_end_of_a_task;
    assign write_fifo_valid         = (write_fifo_rd_en_ff2 && data_fsm_wdesc_en) || (write_fifo_rd_valid_i && ~data_fsm_wdesc_en);
    assign axi_bready_pos           = ~axi_bready_ff && axi_bready;
    
    always @(posedge M_AXI_ACLK_I or negedge M_AXI_ARESETN_I)                                                                              
    begin
        if(~M_AXI_ARESETN_I)
            write_active_total_o <= 1'b0;
        else if(data_fsm_begin_of_a_task && write_active)
            write_active_total_o <= 1'b1;
        else if(data_fsm_end_of_a_task && ~write_active)
            write_active_total_o <= 1'b0;
        else
            write_active_total_o <= write_active_total_o;
    end

    always @(posedge M_AXI_ACLK_I or negedge M_AXI_ARESETN_I)                                                                              
    begin
        if(~M_AXI_ARESETN_I)
            write_data_done <= 1'b0;
        else
            write_data_done <= axi_wlast && wnext;
    end

    localparam integer MEM_MAX_DEPTH = 'd128;
    assign bchk_addr_align = {bchk_addr[39:4], 4'b0};//bchk_addr - (bchk_addr % ADDR_ALIGN_BOUNDARY);

    always @(*)                                   
    begin
        case(w_addr_state)
            W_FETCH_INSTR:
            begin
                if (~instr_fifo_empty && write_transaction_cnt_wch <= $unsigned(AXI_MAX_INSTR_NUM-1))//The fifo is not empty and there are less than AXI_MAX_TRN_NUM pending transactions.
                    instr_fifo_rd_en = 1'b1; //Generate a high level to fetch an write instruction.
                else
                    instr_fifo_rd_en = 1'b0; //Otherwise, do not fetch write instruction.
            end
            default:
                instr_fifo_rd_en = 1'b0;
        endcase                          
    end
    
    always @(*)
    begin
        if (w_addr_state == W_WADDR && axi_awvalid == 1'b1)
            write_len_for_addr_ch = instr_addr_end[AXI_ADDR_WIDTH-1:ADDR2LEN_BIT] - instr_addr[AXI_ADDR_WIDTH-1:ADDR2LEN_BIT];
        else
            write_len_for_addr_ch = 'd0;
    end
    
    always @(*)
    begin
        write_len_for_data_ch = data_fsm_addr_end_part[AXI_ADDR_WIDTH-1:ADDR2LEN_BIT] - data_fsm_addr[AXI_ADDR_WIDTH-1:ADDR2LEN_BIT];
        write_len_total_for_data_ch = data_fsm_addr_end_total[AXI_ADDR_WIDTH-1:ADDR2LEN_BIT] - data_fsm_addr[AXI_ADDR_WIDTH-1:ADDR2LEN_BIT];
    end
    
    always @(posedge M_AXI_ACLK_I or negedge M_AXI_ARESETN_I)
    begin
        if (~M_AXI_ARESETN_I)
        begin
			w_data_state_r            <= 'd0;
            start_write_data_pulse_ff <= 1'b0;
			start_write_data_pulse_r  <= 1'b0;
			bresp_done_ff             <= 1'b0;
			write_index_total_ff      <= 'd0;
			axi_bready_ff             <= 1'b0;
            start_write_r             <= 1'b0;
        end
        else
        begin
			w_data_state_r            <= w_data_state;
            start_write_data_pulse_ff <= start_write_data_pulse;
			start_write_data_pulse_r  <= start_write_data_pulse_ff;
			bresp_done_ff             <= M_AXI_BVALID_I && axi_bready;
			write_index_total_ff      <= write_index_total;
			axi_bready_ff             <= axi_bready;
            start_write_r             <= start_write_i;
	    end
    end
    
    always @(posedge M_AXI_ACLK_I or negedge M_AXI_ARESETN_I)
    begin
       if (~M_AXI_ARESETN_I)
           w_addr_state <= W_IDLE;
       else
           w_addr_state <= w_addr_next_state;
    end
    
    always @(*)
    begin
        case(w_addr_state)
               W_IDLE://01
                   if(~instr_fifo_empty)
                   //if(~instr_fifo_empty || instr_fifo_wr_en == 1'b1)
                       w_addr_next_state = W_FETCH_INSTR;
                   else
                       w_addr_next_state = W_IDLE;
               W_FETCH_INSTR://02
                   if(instr_fifo_empty == 1'b1)
                       w_addr_next_state = W_IDLE;
                   else //instr fifo is not empty
                   begin
                      if(write_transaction_cnt_wch >= AXI_MAX_INSTR_NUM) //suspended transaction to the upper limit, wait first after fetching
                          w_addr_next_state = W_FETCH_INSTR;
                      else //suspended transaction not to upper limmit, write next address
                          w_addr_next_state = W_WADDR;
                   end
               W_WADDR://04
                   begin
                       if(write_addr_done == 1'b1)
                       begin
                           w_addr_next_state = W_FETCH_INSTR;
                       end
                       else
                       begin
                           w_addr_next_state = W_WADDR;
                       end
                   end
              default:
                  w_addr_next_state = W_IDLE;
          endcase
    end

    always @(posedge M_AXI_ACLK_I or negedge M_AXI_ARESETN_I)
    begin
        if (~M_AXI_ARESETN_I)
            w_data_state <= W_IDLE;
        else
            w_data_state <= w_data_next_state;
    end
    
    always @(*)
    begin
        case(w_data_state)
            W_IDLE:
                begin
                    if(write_addr_done == 1'b1)
                        w_data_next_state = W_WDATA;
                    else if (write_transaction_cnt_wch != 'd0) //Enter the WDATA state if the pending transaction is not 0
                        w_data_next_state = W_WDATA;
                    else
                        w_data_next_state = W_IDLE;
                end
            W_WDATA, W_PAUSE:
                begin
                    if(write_data_done == 1'b1)
                    begin
                        if(write_transaction_cnt_wch == 'd1)
                            w_data_next_state = W_IDLE;
                        else
                            w_data_next_state = W_WDATA;
                    end
                    else
                        w_data_next_state = W_WDATA;
                end
            default:
                w_data_next_state = W_IDLE;
        endcase
    end

    always @(posedge M_AXI_ACLK_I or negedge M_AXI_ARESETN_I)
    begin
        if (~M_AXI_ARESETN_I)
            w_bresp_state <= W_IDLE;
        else
            w_bresp_state <= w_bresp_next_state;
    end

    always @(*)
    begin
        case(w_bresp_state)
            W_IDLE, W_PAUSE:
            begin
                if(write_transaction_cnt_bch != 'd0)
                    w_bresp_next_state = W_CHKID;
                else
                    w_bresp_next_state = W_IDLE;
            end
            W_CHKID:
            begin
                if(bresp_done == 1'b1)
                begin
                    if((M_AXI_BID_I == bresp_fsm_id) && (M_AXI_BRESP_I == 2'b00))
                    begin
                        if(write_transaction_cnt_bch == 'd1)
                            w_bresp_next_state = W_IDLE;
                        else
                            w_bresp_next_state = W_CHKID;
                    end
                    else
                        w_bresp_next_state = W_PAUSE;
                end
                else
                    w_bresp_next_state = W_CHKID;
            end
            default:
                w_bresp_next_state = W_IDLE;
        endcase
    end


    always @(posedge M_AXI_ACLK_I or negedge M_AXI_ARESETN_I)                                   
    begin                                                                     
        if (~M_AXI_ARESETN_I)                      
            axi_awvalid   <= 1'b0;
        else if (M_AXI_AWREADY_I && axi_awvalid)// pull ar high, handshake completed, entered wdata state
            axi_awvalid  <= 1'b0;
        else if(((w_addr_state == W_WADDR)) && (write_transaction_cnt_wch < AXI_MAX_INSTR_NUM))
        begin
            if (~axi_awvalid & ~write_addr_done)//Pull aw high and wait for handshake.
                axi_awvalid  <= 1'b1;
            else
                axi_awvalid  <= axi_awvalid;
        end
        else
            axi_awvalid  <= axi_awvalid;//axi_awvalid  <= 1'b0;                           
    end
    
    always @(posedge M_AXI_ACLK_I or negedge M_AXI_ARESETN_I)                                                                              
    begin                                                                                                     
        if (~M_AXI_ARESETN_I)                                                                                 
            write_active <= 1'b0;                                                                           
        else if (start_write_data_pulse_ff)                                                                      
            write_active <= 1'b1;                                        
        else if (axi_wlast && wnext)                                                           
            write_active <= 1'b0;  
        else
            write_active <= write_active;                                                     
    end
    
    always @(posedge M_AXI_ACLK_I or negedge M_AXI_ARESETN_I)
    begin
        if(~M_AXI_ARESETN_I)
        begin
            write_fifo_rd_en_ff1 <= 1'b0;
            write_fifo_rd_en_ff2 <= 1'b0;
        end
        else
        begin
            write_fifo_rd_en_ff1 <= write_fifo_rd_en_o;
            write_fifo_rd_en_ff2 <= write_fifo_rd_en_ff1;
        end
    end

    always @(posedge M_AXI_ACLK_I or negedge M_AXI_ARESETN_I)
    begin
        if(~M_AXI_ARESETN_I)
        begin
            write_fifo_rd_en_o <= 1'b0;
            fifo_rd_cnt        <= 'd0;
        end
        else if(start_write_data_pulse_r)
        begin
            write_fifo_rd_en_o <= 1'b0;
            fifo_rd_cnt        <= 'd0;
        end
        else if(write_active && (MEM_MAX_DEPTH >= 'd16 + (fifo_rd_cnt - axi_wr_cnt)) && (fifo_rd_cnt < (data_fsm_bytes+1)))
        begin
            write_fifo_rd_en_o <= 1'b1;
            fifo_rd_cnt        <= fifo_rd_cnt + 'd16;
        end
        else
        begin
            write_fifo_rd_en_o <= 1'b0;
            fifo_rd_cnt        <= fifo_rd_cnt;
        end
    end
    
    always @(posedge M_AXI_ACLK_I or negedge M_AXI_ARESETN_I)
    begin
        if(~M_AXI_ARESETN_I)
            fifo_rd_done_cnt   <= 1'b0;
        else if(start_write_data_pulse_r)
            fifo_rd_done_cnt   <= 1'b0;
        else if(write_fifo_valid)
            fifo_rd_done_cnt   <= fifo_rd_done_cnt + 'd16;
        else
            fifo_rd_done_cnt   <= fifo_rd_done_cnt;
    end
    
    always @(posedge M_AXI_ACLK_I or negedge M_AXI_ARESETN_I)
    begin
        if(~M_AXI_ARESETN_I)
            write_fifo_valid_ff <= 1'b0;
        else
            write_fifo_valid_ff <= write_fifo_valid;
    end
    
    reg[8 : 0]   cache_wr_state;
    reg[8 : 0]   cache_rd_state;
    reg[8 : 0]   cache_wr_next_state;
    reg[8 : 0]   cache_rd_next_state;

    localparam CACHE_IDLE = 9'b000000001;
    localparam CACHE_0    = 9'b000000010;
    localparam CACHE_1    = 9'b000000100;
    localparam CACHE_2    = 9'b000001000;
    localparam CACHE_3    = 9'b000010000;
    localparam CACHE_4    = 9'b000100000;
    localparam CACHE_5    = 9'b001000000;
    localparam CACHE_6    = 9'b010000000;
    localparam CACHE_7    = 9'b100000000;

    reg[8 : 0] NEXT_WR_CACHE;
    reg[8 : 0] NEXT_RD_CACHE;
    
    always @(*)
    begin
      case(cache_rd_state)
        CACHE_IDLE: NEXT_RD_CACHE = CACHE_0;
        CACHE_0: NEXT_RD_CACHE = CACHE_1;
        CACHE_1: NEXT_RD_CACHE = CACHE_2;
        CACHE_2: NEXT_RD_CACHE = CACHE_3;
        CACHE_3: NEXT_RD_CACHE = CACHE_4;
        CACHE_4: NEXT_RD_CACHE = CACHE_5;
        CACHE_5: NEXT_RD_CACHE = CACHE_6;
        CACHE_6: NEXT_RD_CACHE = CACHE_7;
        CACHE_7: NEXT_RD_CACHE = CACHE_0;
        default: NEXT_RD_CACHE = CACHE_0;
      endcase
    end

    always @(*)
    begin
      case(cache_wr_state)
        CACHE_IDLE: NEXT_WR_CACHE = CACHE_0;
        CACHE_0: NEXT_WR_CACHE = CACHE_1;
        CACHE_1: NEXT_WR_CACHE = CACHE_2;
        CACHE_2: NEXT_WR_CACHE = CACHE_3;
        CACHE_3: NEXT_WR_CACHE = CACHE_4;
        CACHE_4: NEXT_WR_CACHE = CACHE_5;
        CACHE_5: NEXT_WR_CACHE = CACHE_6;
        CACHE_6: NEXT_WR_CACHE = CACHE_7;
        CACHE_7: NEXT_WR_CACHE = CACHE_0;
        default: NEXT_WR_CACHE = CACHE_0;
      endcase
    end

    reg  [AXI_DATA_WIDTH-1 : 0] write_cache_0;
    reg  [AXI_DATA_WIDTH-1 : 0] write_cache_1;
    reg  [AXI_DATA_WIDTH-1 : 0] write_cache_2;
    reg  [AXI_DATA_WIDTH-1 : 0] write_cache_3;
    
    reg  [AXI_DATA_WIDTH-1 : 0] write_cache_4;
    reg  [AXI_DATA_WIDTH-1 : 0] write_cache_5;
    reg  [AXI_DATA_WIDTH-1 : 0] write_cache_6;
    reg  [AXI_DATA_WIDTH-1 : 0] write_cache_7;
    
    reg  [AXI_DATA_WIDTH-1 : 0] next_rd_cache_reg;

    always @(*)
    begin
       case(cache_rd_state)
        CACHE_IDLE: next_rd_cache_reg = write_cache_0;
        CACHE_0: next_rd_cache_reg = write_cache_1;
        CACHE_1: next_rd_cache_reg = write_cache_2;
        CACHE_2: next_rd_cache_reg = write_cache_3;
        CACHE_3: next_rd_cache_reg = write_cache_4;
        CACHE_4: next_rd_cache_reg = write_cache_5;
        CACHE_5: next_rd_cache_reg = write_cache_6;
        CACHE_6: next_rd_cache_reg = write_cache_7;
        CACHE_7: next_rd_cache_reg = write_cache_0;
        default: next_rd_cache_reg = write_cache_0;
      endcase
    end

    always @(posedge M_AXI_ACLK_I or negedge M_AXI_ARESETN_I)
    begin
        if (~M_AXI_ARESETN_I)
            cache_wr_state <= CACHE_IDLE;
        else
            cache_wr_state <= cache_wr_next_state;
    end

    always @(posedge M_AXI_ACLK_I or negedge M_AXI_ARESETN_I)                                                      
    begin                                                                             
        if (~M_AXI_ARESETN_I)
        begin
            write_cache_0  <= 'd0;
            write_cache_1  <= 'd0;
            write_cache_2  <= 'd0;
            write_cache_3  <= 'd0;
            write_cache_4  <= 'd0;
            write_cache_5  <= 'd0;
            write_cache_6  <= 'd0;
            write_cache_7  <= 'd0;
        end
        else if(write_fifo_valid)
        begin
            case (cache_wr_state)
                CACHE_IDLE: write_cache_0 <= write_data_i;
                CACHE_0:    write_cache_1 <= write_data_i;
                CACHE_1:    write_cache_2 <= write_data_i;
                CACHE_2:    write_cache_3 <= write_data_i;
                CACHE_3:    write_cache_4 <= write_data_i;
                CACHE_4:    write_cache_5 <= write_data_i;
                CACHE_5:    write_cache_6 <= write_data_i;
                CACHE_6:    write_cache_7 <= write_data_i;
                CACHE_7:    write_cache_0 <= write_data_i;
                default:    write_cache_0 <= write_data_i;
            endcase
        end
    end

    always @(*)
    begin
        case (cache_wr_state)
            CACHE_IDLE:
            begin
                if(start_write_data_pulse_r)
                    cache_wr_next_state = CACHE_IDLE;
                else if(write_fifo_valid)
                    cache_wr_next_state = CACHE_0;
                else
                    cache_wr_next_state = CACHE_IDLE;
            end
            CACHE_0, CACHE_1, CACHE_2, CACHE_3,
            CACHE_4, CACHE_5, CACHE_6, CACHE_7:
            begin
                if(start_write_data_pulse_r)
                    cache_wr_next_state = CACHE_IDLE;
                else if(write_fifo_valid)
                    cache_wr_next_state = NEXT_WR_CACHE;
                else
                    cache_wr_next_state = cache_wr_state;
            end           
            default:
                cache_wr_next_state = CACHE_IDLE;
        endcase
    end

    always @(posedge M_AXI_ACLK_I or negedge M_AXI_ARESETN_I)                                                      
    begin                                                                             
        if (~M_AXI_ARESETN_I)
            cache_rd_state <= CACHE_IDLE;
        else
            cache_rd_state <= cache_rd_next_state;
    end

    always @(*)
    begin
        case (cache_rd_state)
            CACHE_IDLE:
            begin
                if(start_write_data_pulse_r)
                    cache_rd_next_state = CACHE_IDLE;
                else if(write_active && (axi_wr_cnt < fifo_rd_done_cnt)) //put new data
                    cache_rd_next_state = CACHE_0;
                else
                    cache_rd_next_state = CACHE_IDLE;
            end
            CACHE_0, CACHE_1, CACHE_2, CACHE_3,
            CACHE_4, CACHE_5, CACHE_6, CACHE_7:
            begin
                if(start_write_data_pulse_r)
                    cache_rd_next_state = CACHE_IDLE;
                else if(wnext) //last data is accpted
                begin
                    if(((axi_wr_cnt < fifo_rd_done_cnt) || write_fifo_valid_ff) && (write_index < {1'b0, write_len_for_data_ch})) //put new data
                        cache_rd_next_state = NEXT_RD_CACHE;
                    else if(write_index == {1'b0, write_len_for_data_ch}) //end of a total transaction
                        cache_rd_next_state = CACHE_IDLE;
                    else //new data has not come, wait
                        cache_rd_next_state = cache_rd_state;
                end
                else if((axi_wr_cnt < fifo_rd_done_cnt) && last_data_accepted)
                    cache_rd_next_state = NEXT_RD_CACHE;
                else //data has not been accepted by slave, set wvalid and wait.
                    cache_rd_next_state = cache_rd_state;
            end 
            default:
                cache_rd_next_state = CACHE_IDLE;
        endcase
    end

    always @(posedge M_AXI_ACLK_I or negedge M_AXI_ARESETN_I)
    begin
        if (~M_AXI_ARESETN_I)
        begin
            wdata               <= 'd0;
            axi_wvalid          <= 1'b0;
            axi_wlast           <= 1'b0;
            axi_wstrb           <= 'h0;
            axi_wr_cnt          <= 'd0;
            last_data_accepted  <= 'd0;
        end
        else
        begin
            case (cache_rd_state)
                CACHE_IDLE:
                begin
                    if(start_write_data_pulse_r)
                    begin
                        axi_wvalid          <= 1'b0;
                        axi_wlast           <= 1'b0;
                        axi_wstrb           <= 'h0;
                        axi_wr_cnt          <= 'd0;
                        last_data_accepted  <= 'd0;
                    end
                    else if(write_active && (axi_wr_cnt < fifo_rd_done_cnt)) //put new data
                    begin
                        wdata          <= next_rd_cache_reg;
                        axi_wvalid     <= 1'b1;
                        if(write_len_for_data_ch == 'd0)
                        begin
                            axi_wlast  <= 1'b1;
                            axi_wstrb  <= wstrb_last;
                            axi_wr_cnt <= axi_wr_cnt + last_data_valid_byte;
                        end
                        else
                        begin
                            axi_wlast  <= 1'b0;
                            axi_wstrb  <= {(AXI_DATA_WIDTH/8){1'b1}};
                            axi_wr_cnt <= axi_wr_cnt + (AXI_DATA_WIDTH/8);
                        end
                    end
                    else
                    begin
                        axi_wvalid     <= 1'b0;
                        axi_wr_cnt     <= 'd0;
                    end
                end
               
            CACHE_0, CACHE_1, CACHE_2, CACHE_3,
            CACHE_4, CACHE_5, CACHE_6, CACHE_7:
                begin
                    if(start_write_data_pulse_r)
                    begin
                        axi_wvalid          <= 1'b0;
                        axi_wlast           <= 1'b0;
                        axi_wstrb           <= 'h0;
                        axi_wr_cnt          <= 'd0;
                        last_data_accepted  <= 'd0;
                    end
                    else if(wnext || last_data_accepted) //last data is accpted
                    begin
                        if(((axi_wr_cnt < fifo_rd_done_cnt) || write_fifo_valid_ff) && (write_index < {1'b0, write_len_for_data_ch})) //put new data
                        begin
                            last_data_accepted <= 1'b0;
                            wdata          <= next_rd_cache_reg;
                            axi_wvalid     <= 1'b1;
                            if(wnext && (write_index == write_len_for_data_ch-'d1)) //end of part of transaction
                            begin
                                axi_wlast      <= 1'b1;
                                axi_wstrb      <= wstrb_last;
                                axi_wr_cnt     <= axi_wr_cnt + last_data_valid_byte;
                            end
                            else //not end
                            begin
                                axi_wlast      <= 1'b0;
                                axi_wstrb      <= {(AXI_DATA_WIDTH/8){1'b1}};
                                axi_wr_cnt     <= axi_wr_cnt + (AXI_DATA_WIDTH/8);
                            end
                        end
                        else if(write_index == {1'b0, write_len_for_data_ch} &&
                                (axi_wr_cnt < fifo_rd_done_cnt) && last_data_accepted)
                        begin
                            last_data_accepted <= 1'b0;
                            wdata          <= next_rd_cache_reg;
                            axi_wvalid     <= 1'b1;
                            axi_wlast      <= 1'b1;
                            axi_wstrb      <= wstrb_last;
                            axi_wr_cnt     <= axi_wr_cnt + last_data_valid_byte;
                        end
                        else if(write_index == {1'b0, write_len_for_data_ch} && wnext) //end of a total transaction
                        begin
                            axi_wvalid     <= 1'b0;
                            axi_wlast      <= 1'b0;
                            axi_wr_cnt     <= 'd0;
                        end
                        else //after wnext, new data has not come, wait
                        begin
                            axi_wvalid     <= 1'b0;
                            axi_wr_cnt     <= axi_wr_cnt;
                            last_data_accepted <= 1'b1;
                        end
                    end
                    else //data has not been accepted by slave, set wvalid and wait.
                    begin
                        axi_wvalid     <= 1'b1;
                        axi_wr_cnt     <= axi_wr_cnt;
                    end
                end
                default:
                begin
                    axi_wvalid     <= 1'b0;
                    axi_wlast      <= 1'b0;
                    axi_wstrb      <= 'h0;
                    axi_wr_cnt     <= 'd0;
                    last_data_accepted <= 1'b0;
                end
            endcase
        end
    end

    always @(posedge M_AXI_ACLK_I or negedge M_AXI_ARESETN_I)                                                      
    begin                                                                             
        if (~M_AXI_ARESETN_I)
            write_index <= 'd0;  
        else if(start_write_data_pulse)                                                             
            write_index <= 'd0;                                                       
        else if (wnext && (write_index != {1'b0, write_len_for_data_ch}))
            write_index <= write_index + 1;                                           
        else                                                                            
            write_index <= write_index;                                                   
    end
    
    always @(posedge M_AXI_ACLK_I or negedge M_AXI_ARESETN_I)                                                      
    begin                                                                             
        if (~M_AXI_ARESETN_I)                                                             
            write_index_total <= 'd0;
        else if(start_write_data_pulse_r && data_fsm_begin_of_a_task)  
            write_index_total <= 'd0;                                      
        else if (wnext && (write_index_total != write_len_total_for_data_ch + 'd1))                                   
            write_index_total <= write_index_total + 1;                                           
        else                                                                            
            write_index_total <= write_index_total;                                                   
    end

    always @(posedge M_AXI_ACLK_I or negedge M_AXI_ARESETN_I)                                     
    begin                                                                 
        if (~M_AXI_ARESETN_I)
            axi_bready <= 1'b0;
        else if (M_AXI_BVALID_I && axi_bready)                                
            axi_bready <= 1'b0;  
        else if(w_bresp_state == W_CHKID)
            axi_bready <= 1'b1;         
        else                                                                
            axi_bready <= axi_bready;                                         
    end
    
    always @(posedge M_AXI_ACLK_I or negedge M_AXI_ARESETN_I)                                                                              
    begin
        if(~M_AXI_ARESETN_I)
            write_almost_done <= 1'b0;
        else if(start_write_data_pulse_r && data_fsm_begin_of_a_task)
            write_almost_done <= 1'b0;
        else if(write_data_done)
            write_almost_done <= 1'b0;
        else if(write_active && (write_len_total_for_data_ch <= 'd8 + write_index_total))
            write_almost_done <= 1'b1;
        else
            write_almost_done <= write_almost_done;
    end
    
    always @(posedge M_AXI_ACLK_I or negedge M_AXI_ARESETN_I)                                                                              
    begin
        if(~M_AXI_ARESETN_I)
            write_almost_done_ff <= 1'b0;
        else
            write_almost_done_ff <= write_almost_done;
    end

    axi_boundary_checker #
    (
        .CROSS_4K_EN         (1'b1),
        .AXI_ID_WIDTH        (AXI_ID_WIDTH),
        .AXI_ADDR_WIDTH      (AXI_ADDR_WIDTH),
        .AXI_DATA_WIDTH      (AXI_DATA_WIDTH),
        .AXI_LIB_WIDTH       (AXI_LIB_WIDTH)
    )
    write_boundary_checker
    (
        .clk               (M_AXI_ACLK_I),
        .rstn              (M_AXI_ARESETN_I),
        .id_i              (write_id_i),
        .addr_i            (write_addr_i),
        .wbytes_i          (write_len_in_byte_i),
        .desc_en_i         (write_desc_en_i),
        .desc_type_i       (write_desc_type_i),
        .fifo_sel_i        (1'b0),
        .din_ready         (bchk_ready),
        .din_valid         (start_write_i),
        .id_o              (bchk_id),
        .addr_o            (bchk_addr),
        .wbytes_o          (bchk_bytes),
        .wbytes_total_o    (bchk_bytes_total),
        .desc_en_o         (bchk_wdesc_en),
        .desc_type_o       (bchk_wdesc_type),
        .fifo_sel_o        (),
        .begin_of_a_task   (bchk_begin_of_a_task),
        .end_of_a_task     (bchk_end_of_a_task),
        .dout_ready        (~instr_fifo_full),
        .dout_valid        (instr_fifo_wr_en)
    );
`ifdef VCS_MODEL
    //axi_sfifo #(.DW (INSTR_WIDTH),  .AW (TRN_CNT_WITDH-1)) 
    axi_sfifo_2T #(.DW (INSTR_WIDTH),  .AW (TRN_CNT_WITDH-1)) 
    write_addr_channel_instr_fifo
    (
        .clk         (M_AXI_ACLK_I),
        .rstn        (M_AXI_ARESETN_I),
        .wr_en       (instr_fifo_wr_en),
        .rd_en       (instr_fifo_rd_en),
        .din         ({bchk_id, bchk_addr_align, bchk_bytes}),
        .dout        ({instr_id, instr_addr, instr_bytes}),
        .empty       (instr_fifo_empty),
        .full        (),
        .almost_full (instr_fifo_full)
    );
   
    axi_sfifo_2T #(.DW (DATAFSM_FIFO_DW),  .AW (TRN_CNT_WITDH)) 
    write_data_channel_instr_fifo
    (
        .clk         (M_AXI_ACLK_I),
        .rstn        (M_AXI_ARESETN_I),
        .wr_en       (instr_fifo_wr_en),
        .rd_en       (start_write_data_pulse),
        .din         ({bchk_end_of_a_task, bchk_begin_of_a_task, bchk_wdesc_type, bchk_wdesc_en, bchk_addr_align, bchk_bytes_total, bchk_bytes}),
        .dout        ({data_fsm_end_of_a_task, data_fsm_begin_of_a_task, data_fsm_wdesc_type, data_fsm_wdesc_en, data_fsm_addr, data_fsm_bytes_total, data_fsm_bytes}),
        .empty       (),
        .full        (),
        .almost_full ()
    );
    
    axi_sfifo_2T #(.DW (BRESPFSM_FIFO_DW),  .AW (TRN_CNT_WITDH)) 
    write_response_channel_instr_fifo
    (
        .clk         (M_AXI_ACLK_I),
        .rstn        (M_AXI_ARESETN_I),
        .wr_en       (instr_fifo_wr_en),
        .rd_en       (axi_bready_pos),
        .din         (bchk_id),
        .dout        (bresp_fsm_id),//{bresp_fsm_end_of_a_task, bresp_fsm_id}
        .empty       (),
        .full        (),
        .almost_full ()
    );
`else
    sync_fifo_d32_w57 write_addr_channel_instr_fifo
    (
        .clk         (M_AXI_ACLK_I),
        .rstn        (M_AXI_ARESETN_I),
        .wr_en       (instr_fifo_wr_en),
        .rd_en       (instr_fifo_rd_en),
        .din         ({bchk_id, bchk_addr_align, bchk_bytes}),
        .dout        ({instr_id, instr_addr, instr_bytes}),
        .empty       (instr_fifo_empty),
        .full        (),
        .almost_full (instr_fifo_full),
        .rf_2p_cfg_register    (rf_2p_cfg_register)
    );
   
    sync_fifo_d64_w66 write_data_channel_instr_fifo
    (
        .clk         (M_AXI_ACLK_I),
        .rstn        (M_AXI_ARESETN_I),
        .wr_en       (instr_fifo_wr_en),
        .rd_en       (start_write_data_pulse),
        .din         ({bchk_end_of_a_task, bchk_begin_of_a_task, bchk_wdesc_type, bchk_wdesc_en, bchk_addr_align, bchk_bytes_total, bchk_bytes}),
        .dout        ({data_fsm_end_of_a_task, data_fsm_begin_of_a_task, data_fsm_wdesc_type, data_fsm_wdesc_en, data_fsm_addr, data_fsm_bytes_total, data_fsm_bytes}),
        .empty       (),
        .full        (),
        .almost_full (),
        .ram_2p_cfg_register    (ram_2p_cfg_register)
    );
    
    sync_fifo_d64_w6 write_response_channel_instr_fifo
    (
        .clk         (M_AXI_ACLK_I),
        .rstn        (M_AXI_ARESETN_I),
        .wr_en       (instr_fifo_wr_en),
        .rd_en       (axi_bready_pos),
        .din         (bchk_id),
        .dout        (bresp_fsm_id),//{bresp_fsm_end_of_a_task, bresp_fsm_id}
        .empty       (),
        .full        (),
        .almost_full ()
        //.ram_2p_cfg_register    (ram_2p_cfg_register)
    );
`endif



    axi_instr_counter #(.COUNTER_WIDTH (TRN_CNT_WITDH)) write_transaction_counter_wch
    (
        .clk       (M_AXI_ACLK_I),
        .rstn      (M_AXI_ARESETN_I),
        .add       (write_addr_done),
        .sub       (write_data_done),
        .cnt       (write_transaction_cnt_wch)
    );
    
    axi_instr_counter #(.COUNTER_WIDTH (TRN_CNT_WITDH)) write_transaction_counter_bch
    (
        .clk       (M_AXI_ACLK_I),
        .rstn      (M_AXI_ARESETN_I),
        .add       (write_addr_done),
        .sub       (bresp_done),
        .cnt       (write_transaction_cnt_bch)
    );
    
endmodule
